/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
aLONG with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

/**
 * System Includes:
 **/
#include "commonheaders.h"
#include "ctrl.h"

/**
 * name:   DBGetSettingStr
 * desc:   reads a setting from database. String type is defined by the type member of pdbv
 * param:  hContact   - handle to the contact
 *         pszModule  - the module to read the setting from (e.g. USERINFO)
 *         pszSetting - the setting to read
 *		   pdbv		  - pointer to the DBVARIANT retrieving the value
 * return: 0 on success or 1 on failure
 **/
INT DBGetSettingStr(HANDLE hContact, LPCSTR pszModule, LPCSTR pszSetting, DBVARIANT *pdbv)
{
	INT rc = 1;

	if(pdbv) {
		DBCONTACTGETSETTING cgs;

		cgs.szSetting = pszSetting;
		cgs.szModule = pszModule;
		cgs.pValue = pdbv;
		rc = CallService(MS_DB_CONTACT_GETSETTING_STR, (WPARAM)hContact, (LPARAM)&cgs);
		if(rc) {
			pdbv->type = DBVT_DELETED;
			pdbv->pbVal = NULL;
		}
	}
	return rc;
}

/**
 * name:   DBGetSettingAS
 * desc:   reads a setting from database. Returned string type is TCHAR.
 * param:  hContact   - handle to the contact
 *         pszModule  - the module to read the setting from (e.g. USERINFO)
 *         pszSetting - the setting to read
 *		   pdbv		  - pointer to the DBVARIANT retrieving the value
 * return: 0 on success or 1 on failure
 **/
INT DBGetSettingAS(HANDLE hContact, LPCSTR pszModule, LPCSTR pszSetting, DBVARIANT *pdbv)
{
	if(pdbv) {
		pdbv->type = DBVT_ASCIIZ;
		return DBGetSettingStr(hContact, pszModule, pszSetting, pdbv);  
	}
	return 1;
}

/**
 * name:   DBGetSettingTS
 * desc:   reads a setting from database. Returned string type is TCHAR.
 * param:  hContact   - handle to the contact
 *         pszModule  - the module to read the setting from (e.g. USERINFO)
 *         pszSetting - the setting to read
 *		   pdbv		  - pointer to the DBVARIANT retrieving the value
 * return: 0 on success or 1 on failure
 **/
INT DBGetSettingTS(HANDLE hContact, LPCSTR pszModule, LPCSTR pszSetting, DBVARIANT *pdbv)
{
	if(pdbv) {
		pdbv->type = DBVT_TCHAR;
		return DBGetSettingStr(hContact, pszModule, pszSetting, pdbv);  
	}
	return 1;
}

/**
 * name:   DBGetSettingUtf8
 * desc:   reads a setting from database. Returned string type is UTF8.
 * param:  hContact   - handle to the contact
 *         pszModule  - the module to read the setting from (e.g. USERINFO)
 *         pszSetting - the setting to read
 *		   pdbv		  - pointer to the DBVARIANT retrieving the value
 * return: 0 on success or 1 on failure
 **/
INT DBGetSettingUtf8(HANDLE hContact, LPCSTR pszModule, LPCSTR pszSetting, DBVARIANT *pdbv)
{
	if(pdbv) {
		pdbv->type = DBVT_UTF8;
		return DBGetSettingStr(hContact, pszModule, pszSetting, pdbv);  
	}
	return 1;
}

/**
 * name:   DBGetSettingByte
 * desc:   reads a setting from database and returns a corresponding byte
 * param:  hContact   - handle to the contact
 *         szModule   - the module to read the setting from (e.g. USERINFO)
 *         szSetting  - the setting to read
 *         errorValue - value to return if something goes wrong
 * return: byte to read
 **/
BYTE DBGetSettingByte(HANDLE hContact, LPCSTR szModule, LPCSTR szSetting, BYTE errorValue)
{
	DBVARIANT dbv;

	dbv.type = DBVT_DELETED;
	if(!DBGetSettingStr(hContact, szModule, szSetting, &dbv)) {
		switch(dbv.type) {
			case DBVT_BYTE:
				return dbv.bVal;
			case DBVT_WORD:
				if(dbv.wVal < 0x0100)
					return (BYTE)dbv.wVal;
				break;
			case DBVT_DWORD:
				if(dbv.dVal < 0x00000100)
					return (BYTE)dbv.dVal;
				break;
			default:
				DBFreeVariant(&dbv);
				break;
		}
	}
	return errorValue;
}

/**
 * name:   DBGetSettingByte
 * desc:   reads a setting from database and returns a corresponding word
 * param:  hContact   - handle to the contact
 *         szModule   - the module to read the setting from (e.g. USERINFO)
 *         szSetting  - the setting to read
 *         errorValue - value to return if something goes wrong
 * return: word to read
 **/
WORD DBGetSettingWord(HANDLE hContact, LPCSTR szModule, LPCSTR szSetting, WORD errorValue)
{
	DBVARIANT dbv;

	dbv.type = DBVT_DELETED;
	if(!DBGetSettingStr(hContact, szModule, szSetting, &dbv)) {
		switch(dbv.type) {
			case DBVT_BYTE:
				return (WORD)dbv.bVal;
			case DBVT_WORD:
				return dbv.wVal;
			case DBVT_DWORD:
				if(dbv.dVal < 0x00010000)
					return (WORD)dbv.dVal;
			default:
				DBFreeVariant(&dbv);
				break;
		}
	}
	return errorValue;
}

/**
 * name:	DBGetSettingCtrlUnicode
 * desc:	reads a setting from both protcol and userinfo module and returns value & flags.
 *			Strings are converted to widechar if database and operating system support it.
 *			Characterset of project (MBCS/UNICODE) does not have any effect.
 * param:	hContact	- handle to the contact
 *			szModule	- the module to read the setting from (e.g. USERINFO)
 *			szSubModule	- the modutle of a metaContact to get the setting from
 *			szBaseProto	- the base protocol to read the setting from if it was not found in szModule
 *			szSetting	- the setting to read
 *			dbv			- DBVARIANT structure that holds the result
 * return:	wFlag, telling the caller where the data is read from. It is an control flag
 **/
WORD DBGetSettingCtrlUnicode(HANDLE hContact, LPCSTR szModule, LPCSTR szSubModule, LPCSTR szBaseProto, LPCSTR szSetting, DBVARIANT* dbv)
{
	WORD wFlags = 0;
	INT i, numSubs, iDefault;
	HANDLE hSubContact;

	// read setting from given module
	if(hContact && szModule && *szModule && !DBGetSettingTS(hContact, szModule, szSetting, dbv)) {
		wFlags |= CTRLF_HASCUSTOM;
		if(DBSettingExists(hContact, szBaseProto, szSetting))
			wFlags |= CTRLF_HASPROTO;
		return wFlags;
	}
	// read setting from contact's basic protocol
	if(szBaseProto && *szBaseProto) {	
		// try to read the setting from the basic protocol
		if(!DBGetSettingTS(hContact, szBaseProto, szSetting, dbv))
			wFlags |= CTRLF_HASPROTO;
		
		// try to read the setting from the sub contacts' modules
		else
		if(IsMetaContactProtoWantToScan(szBaseProto)) {
			// try to read setting from the default sub contact first
			if(
				(iDefault = DBGetSettingDword(hContact, META_PROTO, SET_META_DEFAULTNUM, -1)) != -1 &&
				(hSubContact = (HANDLE)CallService(MS_MC_GETSUBCONTACT, (WPARAM)hContact, iDefault)) != 0 &&
				(szBaseProto = DBGetContactBaseProto(hSubContact)) != 0 &&
				(wFlags = DBGetSettingCtrlUnicode(hSubContact, szSubModule, NULL, szBaseProto, szSetting, dbv)) != 0
			  )
			{
				wFlags &= ~CTRLF_HASCUSTOM;
				wFlags |= CTRLF_HASMETA;
			}
			// copy the missing settings from the other subcontacts
			else {
				numSubs = DBGetSettingDword(hContact, META_PROTO, SET_META_NUMCONTACTS, -1);
				for(i = 0; i < numSubs; i++) {
					if(i == iDefault) continue;
					if(!(hSubContact = (HANDLE)CallService(MS_MC_GETSUBCONTACT, (WPARAM)hContact, i))) continue;
					if(!(szBaseProto = DBGetContactBaseProto(hSubContact))) continue;
					if(wFlags = DBGetSettingCtrlUnicode(hSubContact, szSubModule, NULL, szBaseProto, szSetting, dbv)) {
						wFlags &= ~CTRLF_HASCUSTOM;
						wFlags |= CTRLF_HASMETA;
						break;
					}
				}
			}
		}
		else
			dbv->type = DBVT_DELETED;
	}
	return wFlags;
}

/**
 * name:	DBGetSettingStaticString
 * desc:	read a setting from database into a predefined portion of memory and convert numbers into a string, too
 * param:	hContact	- handle to the contact
 *			szModule	- the module to read the setting from (e.g. USERINFO)
 *			szSetting	- the setting to read
 *			szValue		- buffer, that retrieves the value
 *			cchValue	- number of characters the buffer can take
 * return:	0 on success, nonzero on failure
 **/
INT DBGetSettingStaticString(HANDLE hContact, LPCSTR szModule, LPCSTR szSetting, LPSTR szValue, INT cchValue)
{
	DBVARIANT dbv;
	DBCONTACTGETSETTING sVal;

	if(!szValue) return 1;

	*szValue = 0;
	dbv.pszVal = szValue;
	dbv.cchVal = cchValue;
	dbv.type = DBVT_ASCIIZ;
	sVal.pValue = &dbv;
	sVal.szModule = szModule;
	sVal.szSetting = szSetting;
	if(!CallService(MS_DB_CONTACT_GETSETTINGSTATIC, (WPARAM)hContact, (LPARAM)&sVal)) {
		switch(dbv.type) {
			case DBVT_BYTE:
				_itoa(dbv.bVal, szValue, 10);
				break;
			case DBVT_WORD:
				_itoa(dbv.wVal, szValue, 10);
				break;
			case DBVT_DWORD:
				_itoa(dbv.dVal, szValue, 10);
				break;
		}
		return (*szValue == 0);
	}
	return 1;
}


INT DBGetSettingEx(HANDLE hContact, LPCSTR pszModule, LPCSTR pszBaseProto, LPCSTR pszSetting, DBVARIANT* pdbv)
{
	pdbv->type = DBVT_ASCIIZ;
	return DBGetSettingStrEx(hContact, pszModule, pszBaseProto, pszSetting, pdbv);
}

/**
 * name:	DBGetSettingStrEx
 * desc:	This function tries to read a setting from a certain module (e.g. USERINFO) and if failed it 
 *			tries once again with the baseprotocol of the contact (e.g. ICQ)
 * params:	hContact	 - handle of the contact whose setting to read
 *			pszModule	 - desired module
 *			pszBaseProto - the protocol to fall back to
 *			pszSetting	 - the desired setting to read
 *			pdbv		 - pointer to a DBVARIANT structure retrieving the result, the type member must be set to the desired string format!
 * return:	0 if setting was found and read correctly, 1 if any error occured or setting was not found
 **/
INT DBGetSettingStrEx(HANDLE hContact, LPCSTR pszModule, LPCSTR pszBaseProto, LPCSTR pszSetting, DBVARIANT *pdbv)
{
	DBCONTACTGETSETTING cgs;
	BYTE type = pdbv->type;
	
	cgs.szSetting = pszSetting;

	// try to get setting from desired source
	cgs.szModule = pszModule;
	cgs.pValue = pdbv;
	if(pszModule && !CallService(MS_DB_CONTACT_GETSETTING_STR, (WPARAM)hContact, (LPARAM)&cgs))
		return 0;

	// try to get setting from basic protocol module
	if(pszBaseProto) {
		cgs.szModule = pszBaseProto;
		cgs.pValue->type = type;
		return CallService(MS_DB_CONTACT_GETSETTING_STR, (WPARAM)hContact, (LPARAM)&cgs);
	}
	return 1;
}

/**
 * name:	DBSettingExists
 * desc:	tells whether a setting exists or not
 * param:	hContact	- handle to the contact
 *			szModule	- the module to read the setting from (e.g. USERINFO)
 *			szSetting	- the setting to read
 * return:	TRUE if exists, FALSE otherwise
 **/
BOOLEAN DBSettingExists(HANDLE hContact, LPCSTR szModule, LPCSTR szSetting)
{
	DBCONTACTGETSETTING sVal;
	DBVARIANT dbv;
	CHAR szDummy[1];

	if(!szModule || !szSetting) 
		return FALSE;
	dbv.pszVal = szDummy;
	dbv.cchVal = sizeof(szDummy);
	dbv.type = DBVT_ASCIIZ;
	sVal.pValue = &dbv;
	sVal.szModule = szModule;
	sVal.szSetting = szSetting;
	return (!CallService(MS_DB_CONTACT_GETSETTINGSTATIC, (WPARAM)hContact, (LPARAM)&sVal) && dbv.type > DBVT_DELETED);
}

/**
 * name:	DBCopyModule
 * desc:	copies all settings from one to another module
 * param:	hContactSrc		- handle to the contact to copy a module from
 *			pszModuleSrc	- the module to copy
 *			hContactDst		- handle to the contact to copy a module to
 *			pszModuleDst	- name of the new module
 *			wFlags			- some options
 * return:	0 if copy is ok
 **/
INT DBCopyModule(HANDLE hContactSrc, LPCSTR pszModuleSrc, HANDLE hContactDst, LPCSTR pszModuleDst, BOOLEAN bForceOverwrite)
{
	ENUMLIST enumSetting;
	LPENUMLISTITEM pi;
	DBCONTACTWRITESETTING cws;
	DBCONTACTGETSETTING cgs;

	if(DBEnumSettings(hContactSrc, pszModuleSrc, &enumSetting)) {
		for(pi = enumSetting.first; pi != NULL; pi = pi->next) {
			if(pi->pszName) {
				cgs.szModule = pszModuleDst;
				cgs.szSetting = pi->pszName;
				cgs.pValue = &cws.value;
				cws.value.type = 0;
				if(!CallService(MS_DB_CONTACT_GETSETTING_STR, (WPARAM)pszModuleSrc, (LPARAM)&cgs)) {
					if(!bForceOverwrite && DBSettingExists(hContactDst, pszModuleDst, pi->pszName))
						continue;
					cws.szModule = pszModuleDst;
					cws.szSetting = pi->pszName;
					CallService(MS_DB_CONTACT_WRITESETTING, (WPARAM)hContactDst, (LPARAM)&cws);				
					DBFreeVariant(&cws.value);
				}
				else
				if(bForceOverwrite)
					DBDeleteContactSetting(hContactDst, pszModuleDst, pi->pszName);
			}
		}
		DBFreeEnumList(&enumSetting);
		return 0;
	}
	return 1;
}

/**
 * name:	DBDeleteModule
 * desc:	deletes all settings in the module
 * param:	hContact	- handle to the contact to delete a module from
 *			pszModule	- the module to delete
 * return:	nothing
 **/
VOID DBDeleteModule(HANDLE hContact, LPCSTR pszModule)
{
	ENUMLIST enumSetting;
	LPENUMLISTITEM pi;

	if(pszModule && DBEnumSettings(hContact, pszModule, &enumSetting)) {
		for(pi = enumSetting.first; pi != NULL; pi = pi->next) {
			if(pi->pszName) DBDeleteContactSetting(hContact, pszModule, pi->pszName);
		}
		DBFreeEnumList(&enumSetting);
	}
}

/**
 * name:	DBDeleteSettingArray
 * desc:	delete all reluctant settings of an setting array such as My-phoneXX
 * param:	hContact	- handle to the contact to delete a module from
 *			pszModule	- the module to delete
 *			pszFormat	- the format, telling what a array of settings is ment
 *			iStart		- the first index of the setting to delete
 * return:	nothing
 **/
VOID DBDeleteSettingArray(HANDLE hContact, LPCSTR pszModule, LPCSTR pszFormat, INT iStart)
{
	CHAR szSetting[MAXSETTING];
	do {
		mir_snprintfA(szSetting, MAXSETTING, pszFormat, iStart++);
	} while(!DBDeleteContactSetting(hContact, pszModule, szSetting));
}

/**
 * name:	DBVariantToStringA
 * desc:	convert an DBVARIANT structure to an ansi string
 * param:	dbv	- pointer to the DBVARIANT to translate
 * return:	pointer to the dbv->pszVal member 
 **/
LPSTR DBVariantToStringA(DBVARIANT *dbv, BOOLEAN bPrepentType)
{
	if(dbv) {
		CHAR buf[32];
		LPSTR tmp;
	
		bPrepentType = bPrepentType != 0 ? 1 : 0;
		switch(dbv->type) {
			case DBVT_BYTE:
				if(bPrepentType) buf[0] = 'b';
				_ultoa(dbv->bVal, buf + bPrepentType, 10);
				return mir_strdup(buf);
			case DBVT_WORD:
				if(bPrepentType) buf[0] = 'w';
				_ultoa(dbv->wVal, buf + bPrepentType, 10);
				return mir_strdup(buf);
			case DBVT_DWORD:
				if(bPrepentType) buf[0] = 'd';
				_ultoa(dbv->dVal, buf + bPrepentType, 10);
				return mir_strdup(buf);
			case DBVT_ASCIIZ:
				if(bPrepentType) {
					tmp = (LPSTR)mir_alloc(mir_strlen(dbv->pszVal) + 2);
					if(tmp == NULL) return NULL;
					tmp[0] = 's';
					strcpy(tmp + 1, dbv->pszVal);
					return tmp;
				}
				return mir_strdup(dbv->pszVal);
			case DBVT_UTF8: {
				DWORD len = Utf8DecodedLength(dbv->pszVal);
				LPSTR tmp = (LPSTR)mir_alloc(len + bPrepentType + 1);
				if(tmp != NULL) {
					if(bPrepentType) 
						tmp[0] = 's';
					if(Utf8ToMultiByteStatic(dbv->pszVal, tmp + bPrepentType, len) > 0)
						return tmp;
					mir_free(tmp);
				}
				break;
			}
			case DBVT_WCHAR: {
				DWORD len = WideCharToMultiByte(myGlobals.CodePage, 0, dbv->pwszVal, -1, NULL, NULL, NULL, NULL);
				LPSTR tmp = (LPSTR)mir_alloc(len + bPrepentType + 1);
				if(tmp != NULL) {
					if(bPrepentType) 
						tmp[0] = 's';
					if(WideCharToMultiByte(myGlobals.CodePage, 0, dbv->pwszVal, -1, tmp + bPrepentType, len, NULL, NULL) > 0)
						return tmp;
					mir_free(tmp);
				}
				break;
			}
		}
	}
	return NULL;
}

/**
 * name:	DBVariantToStringW
 * desc:	convert an DBVARIANT structure to an unicode string
 * param:	dbv	- pointer to the DBVARIANT to translate
 * return:	pointer to the dbv->pwszVal member 
 **/
LPWSTR DBVariantToStringW(DBVARIANT *dbv)
{
	if(dbv) {
		WCHAR buf[32];
		switch(dbv->type) {
			case DBVT_BYTE:
				_ultow(dbv->bVal, buf, 10);
				return mir_wcsdup(buf);
			case DBVT_WORD:
				_ultow(dbv->wVal, buf, 10);
				return mir_wcsdup(buf);
			case DBVT_DWORD:
				_ultow(dbv->dVal, buf, 10);
				return mir_wcsdup(buf);
			case DBVT_ASCIIZ:
				return MultiByteToWideChar(dbv->pszVal);
			case DBVT_UTF8:
				return Utf8ToWideChar(dbv->pszVal);
			case DBVT_WCHAR:
				return mir_wcsdup(dbv->pwszVal);
		}
	}
	return NULL;
}

/**
 * name:	WhenWasContactAdded
 * desc:	have a look into the system event chain and try to find the timestamp
 *			the given contact was added to the clist
 * param:	hContact - handle of the contact whose added date is to find.
 * return:	timestamp the contact was added on success or 0 on failure
 **/
DWORD WhenWasContactAdded(DWORD dwUIN, LPCSTR pszBaseProto)
{
	DBEVENTINFO	dbei; 
	HANDLE		hDbEvent;
	DWORD		dwEvtUIN;

	ZeroMemory(&dbei, sizeof(dbei));
	dbei.cbSize = sizeof(dbei);

	for(hDbEvent = (HANDLE)CallService(MS_DB_EVENT_FINDFIRST, NULL, NULL);
		hDbEvent != NULL;
		hDbEvent = (HANDLE)CallService(MS_DB_EVENT_FINDNEXT, (WPARAM)hDbEvent, NULL))
	{
		dbei.cbBlob = 0;

		// get eventtype and compare
		if(CallService(MS_DB_EVENT_GET, (WPARAM)hDbEvent, (LPARAM)&dbei)) continue;
		if(dbei.eventType != EVENTTYPE_ADDED) continue;
		
		// allocate data memory
		dbei.pBlob = (PBYTE)mir_alloc(dbei.cbBlob);
		if(!dbei.pBlob) break;

		// get event data
		if(CallService(MS_DB_EVENT_GET, (WPARAM)hDbEvent, (LPARAM)&dbei)) continue;
		// extract UIN and compare with given one
		CopyMemory(&dwEvtUIN, dbei.pBlob, sizeof(DWORD));
		if(dwEvtUIN == dwUIN) {
			mir_free(dbei.pBlob);
			return dbei.timestamp;
		}
		mir_free(dbei.pBlob);
	}
	return 0;
}

/**
 * name:	IsDuplicateEvent
 * desc:	find a certain event
 *			the given contact was added to the clist
 * param:	hContact - handle of the contact whose added date is to find.
 * return:	timestamp the contact was added on success or 0 on failure
 **/
BOOL IsDuplicateEvent(HANDLE hContact, DBEVENTINFO dbei)
{
   HANDLE hExistingDbEvent;
   DBEVENTINFO dbeiExisting;

   if(!(hExistingDbEvent = (HANDLE)CallService(MS_DB_EVENT_FINDFIRST, (WPARAM)hContact, 0)))
      return FALSE;
   ZeroMemory(&dbeiExisting, sizeof(dbeiExisting));
   dbeiExisting.cbSize = sizeof(dbeiExisting);
   dbeiExisting.cbBlob = 0;
   CallService(MS_DB_EVENT_GET, (WPARAM)hExistingDbEvent, (LPARAM)&dbeiExisting);
   if(dbei.timestamp < dbeiExisting.timestamp)
      return FALSE;

   if(!(hExistingDbEvent = (HANDLE)CallService(MS_DB_EVENT_FINDLAST, (WPARAM)hContact, 0)))
      return FALSE;
   ZeroMemory(&dbeiExisting, sizeof(dbeiExisting));
   dbeiExisting.cbSize = sizeof(dbeiExisting);
   dbeiExisting.cbBlob = 0;
   CallService(MS_DB_EVENT_GET, (WPARAM)hExistingDbEvent, (LPARAM)&dbeiExisting);
   if(dbei.timestamp > dbeiExisting.timestamp)
      return FALSE;

   while(hExistingDbEvent != NULL) {
      ZeroMemory(&dbeiExisting, sizeof(dbeiExisting));
      dbeiExisting.cbSize = sizeof(dbeiExisting);
      dbeiExisting.cbBlob = 0;
      CallService(MS_DB_EVENT_GET, (WPARAM)hExistingDbEvent, (LPARAM)&dbeiExisting);
      
      if(dbei.timestamp > dbeiExisting.timestamp)
         return FALSE;

        // Compare event with import candidate
      if((dbei.timestamp == dbeiExisting.timestamp) &&
         (dbei.eventType == dbeiExisting.eventType) &&
         (dbei.cbBlob == dbeiExisting.cbBlob)) {
         return TRUE;
      }
      
      // Get next event in chain
      hExistingDbEvent = (HANDLE)CallService(MS_DB_EVENT_FINDPREV, (WPARAM)hExistingDbEvent, 0);
   }

   return FALSE;
}

/**
 * name:	IsMetaContactProtoWantToScan
 * desc:	check if protocol is metacontact proto and contact wants to scan subcontacts
 * param:	pszProto - protocol
 * return:	true if metacontact plugin is installed, proto is META_PROTO and user wants to scan subcontact
 **/
BOOLEAN IsMetaContactProtoWantToScan(LPCSTR pszProto)
{
	return myGlobals.HaveMetaContacts && !mir_stricmp(pszProto, META_PROTO) && DBGetByte(SET_META_SCAN, TRUE);
}

/*
============================================================================================
	enumerating stuff
============================================================================================
*/

INT CheckIfModuleIsEmptyProc(LPCSTR szSetting, LPARAM lParam)
{
	return 1;
}

/**
 * name:	IsModuleEmpty
 * desc:	returns whether the specified module is empty or not
 * param:	hContact	- handle of the contact whose module to check
 *			szModule	- the module to check
 **/
INT IsModuleEmpty(HANDLE hContact, LPCSTR szModule)
{
	DBCONTACTENUMSETTINGS dbces;
	dbces.pfnEnumProc = CheckIfModuleIsEmptyProc;
	dbces.szModule = szModule;
	dbces.ofsSettings = 0;
	dbces.lParam = 0;
	return (0 > CallService(MS_DB_CONTACT_ENUMSETTINGS, (WPARAM)hContact, (LPARAM)&dbces));
}

/**
 * name:	enumListProc
 * desc:	procedure for enumeration services. It creates the list of modules or settings
 * param:	pszName			- the string identifiing the module or setting
 *			ofsModuleName	- ???
 *			lParam			- contains the ENUMLIST structure to add the setting to
 **/
INT enumListProc(LPCSTR pszName, DWORD ofsModuleName, LPARAM lParam)
{
	LPENUMLIST pEnumList = (LPENUMLIST)lParam;
	
	if(!pEnumList->first) {
		pEnumList->first = (LPENUMLISTITEM)malloc(sizeof(ENUMLISTITEM));
		if(!pEnumList->first) return 1;
		pEnumList->first->pszName = _strdup(pszName);
		pEnumList->first->next = 0;
		pEnumList->last = pEnumList->first;
	}
	else {
		LPENUMLISTITEM pEnumItem = (LPENUMLISTITEM)malloc(sizeof(ENUMLISTITEM));
		if(!pEnumItem) return 1;
		pEnumList->last->next = pEnumItem;
		pEnumList->last = pEnumItem;
		pEnumItem->pszName = _strdup(pszName);
		pEnumItem->next = 0;
	}
	return 0;
}

/**
 * name:	enumSettingProc
 * desc:	procedure for enumeration services. It creates the list of settings
 * param:	pszName			- the string identifiing the module or setting
 *			lParam			- contains the ENUMLIST structure to add the setting to
 **/
INT enumSettingsProc(LPCSTR pszName, LPARAM lParam)
{
	return enumListProc(pszName, 0, lParam);
}

/**
 * name:	DBEnumModules
 * desc:	create a list of all modules
 **/
INT DBEnumModules(LPENUMLIST pEnumList)
{
	pEnumList->first = 0;
	pEnumList->last = 0;
	return SUCCEEDED(CallService(MS_DB_MODULES_ENUM, (WPARAM)pEnumList, (WPARAM)enumListProc));
}

/**
 * name:	DBEnumSettings
 * desc:	enum all setting the contact has for the module
 * param:	hContact		- contact whose module to enumerate
 *			pszModule		- module to enumerate
 *			pEnumList		- list, that receives all settings
 **/
INT DBEnumSettings(HANDLE hContact, LPCSTR pszModule, LPENUMLIST pEnumList)
{
	DBCONTACTENUMSETTINGS dbces;
	
	dbces.pfnEnumProc = enumSettingsProc;
	dbces.szModule = pszModule;
	dbces.lParam = (LPARAM)pEnumList;
	dbces.ofsSettings = 0;
	pEnumList->first = 0;
	pEnumList->last = 0;
	return SUCCEEDED(CallService(MS_DB_CONTACT_ENUMSETTINGS, (WPARAM)hContact,(LPARAM)&dbces));
}

/**
 * name:	DBFreeEnumList
 * desc:	free the memory used by the enumeration list
 * param:	pEnumList	- pointer to the enumeration list
 * return	nothing
 **/
VOID DBFreeEnumList(LPENUMLIST pEnumList)
{
    if(pEnumList) {
		LPENUMLISTITEM pItem = pEnumList->first;
		LPENUMLISTITEM pTemp;

		while(pItem) {
			if(pItem->pszName) free((LPVOID)pItem->pszName);
			pTemp = pItem;
			pItem = pItem->next;
			free(pTemp);
		}
		pEnumList->first = 0;
		pEnumList->last = 0;
	}
}

